package org.foo; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; import org.apache.chemistry.opencmis.commons.data.ContentStream; import org.apache.chemistry.opencmis.commons.data.ExtensionsData; import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.chemistry.opencmis.commons.server.CmisService; import org.apache.chemistry.opencmis.commons.server.TempStoreOutputStream; import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory; import org.apache.chemistry.opencmis.server.shared.ThresholdOutputStream; import org.apache.chemistry.opencmis.server.support.wrapper.AbstractCmisServiceWrapper; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.edit.PDPageContentStream; import org.apache.pdfbox.pdmodel.graphics.xobject.PDPixelMap; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObjectImage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Example of a minimal CMIS Custom Service Wrapper (logging example) * * Add the following ** to the repo.properties to have framework hook into chain * The number at the key is the position in the wrapper stack. Lower numbers are * outer wrappers, higher numbers are inner wrappers. * * ** add the following line to your repo.properties file in your servers war * * <pre> * servicewrapper.1=org.apache.chemistry.opencmis.server.support.CmisCustomLoggingServiceWrapper * </pre> */ public class CmisCustomPdfWatermarkServiceWrapper extends AbstractCmisServiceWrapper { // slf4j example private static final Logger LOG = LoggerFactory.getLogger(CmisCustomPdfWatermarkServiceWrapper.class); // constants for this extension public static final String USER_FILTER_NAME = "userfilter"; private String userToWatermark = null; // provide constructor public CmisCustomPdfWatermarkServiceWrapper(CmisService service) { super(service); } /** * Initializes the wrapper with a set of optional parameters from the * properties file */ @Override public void initialize(Object[] params) { // Whenever CmisServiceWrapperManager.wrap() is called, a new wrapper // instance is created and the corresponding initialize() method is // called. // If the service factory creates a new CmisService object and wraps it // with every request, then new wrapper instances are created and // initialized for every request. // This is why in this case you will see initialize get called for every // request. // Depending on your implementation you may only see this called once. LOG.info("Initializing the CmisCustomPdfWatermarkServiceWrapper."); for (Object parm : params) { if (parm == null) { continue; } LOG.info("[CmisCustomPdfWatermarkServiceWrapper]Parameter:" + parm.toString()); // Let's store the ID of the user that we want to flag for // watermarking. String[] parts = parm.toString().split("="); if (USER_FILTER_NAME.equalsIgnoreCase(parts[0])) { // we have a user userToWatermark = parts[1]; } } } /** * slf logging version with dual output to console and slf */ protected void slflog(String operation, String repositoryId) { if (repositoryId == null) { repositoryId = "<none>"; } HttpServletRequest request = (HttpServletRequest) getCallContext().get(CallContext.HTTP_SERVLET_REQUEST); String userAgent = request.getHeader("User-Agent"); if (userAgent == null) { userAgent = "<unknown>"; } String binding = getCallContext().getBinding(); LOG.info("Operation: {}, Repository ID: {}, Binding: {}, User Agent: {}", operation, repositoryId, binding, userAgent); } @Override public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset, BigInteger length, ExtensionsData extension) { slflog("getContentStream override from Chameleon module --------------", repositoryId); long startTime = System.currentTimeMillis(); CallContext sharedContext = this.getCallContext(); // Get the native domain object from the call context if one is shared // by the vendor (example only) // Your CMIS vendor's documentation must expose the name of any shared // objects they place here for extensions. // Object objShared = sharedContext.get("shared_key_name_from_vendor"); ContentStream retVal = getWrappedService().getContentStream(repositoryId, objectId, streamId, offset, length, extension); if (sharedContext.getUsername().equalsIgnoreCase(userToWatermark)) { if ((retVal != null) && (retVal.getMimeType().contains("pdf"))) { InputStream rawStream = retVal.getStream(); // return a pdfbox document object // for debugging only - load to pdfbox and stream out // PDDocument modifiedPDF = watermarkPDF_loadOnly(rawStream); // actual watermark code PDDocument modifiedPDF = watermarkPDF(rawStream); // Extra credit here. Replace with TempStoreOutputStream or find // another way to handle very large objects in a small memory // footprint. // ByteArrayOutputStream out = new ByteArrayOutputStream(); TempStoreOutputStream out; TempStoreOutputStreamFactory outFactory = (TempStoreOutputStreamFactory) sharedContext .get(CallContext.STREAM_FACTORY); if (outFactory != null) { // reuse the server factory configuration out = outFactory.newOutputStream(); } else { // there is no default ThresholdOutputStreamFactory // -> create a stream manually: // default temp directory, max 4MiB in main memory, // unlimited content size out = new ThresholdOutputStream(null, 4 * 1024 * 1024, -1); } try { modifiedPDF.save(out); modifiedPDF.close(); InputStream modifiedInputStream = out.getInputStream(); // new // ByteArrayInputStream(out.toByteArray()); // Extra credit here. Handle offset and length if provided // by the client. // now write the stream back to the ContentStream object retVal = new ContentStreamImpl(retVal.getFileName(), null, "application/pdf", modifiedInputStream); } catch (Exception e) { slflog("error transposing stream getContentStream ", e.getMessage()); LOG.error("Could not watermark PDF document: {}", e.getMessage(), e); throw new CmisRuntimeException("Could not watermark PDF document!"); } } // if pdf stream } // if user matches filter param LOG.info("[CmisCustomServiceWrapper] Exiting method getContentStream. time (ms):" + (System.currentTimeMillis() - startTime)); // System.out.println("[CmisCustomServiceWrapper] Exiting method getContentStream. time (ms):" // + (System.currentTimeMillis() - startTime)); return retVal; } public PDDocument watermarkPDF(InputStream rawStream) { PDDocument pdf = null; try { pdf = PDDocument.load(rawStream); // Load watermark from classes directory cmis.jpg // Alternatives: // 1. Use a pdf document from the repository (via a fixed path) so // the watermark can be changed at runtime by an administrator. // 2. Dynamically generate a watermark based on the username / time // date and users ip address. InputStream watermarkStream = CmisCustomPdfWatermarkServiceWrapper.class.getClassLoader() .getResourceAsStream("cmis.png"); BufferedImage buffered = ImageIO.read(watermarkStream); // Exercise A - add image to all pages of document. // Loop through pages in PDF // List pages = pdf.getDocumentCatalog().getAllPages(); // System.out.println("pdf loaded from stream. Pages found:" + // pages.size()); // Iterator iter = pages.iterator(); // while(iter.hasNext()) // { // PDPage page = (PDPage)iter.next(); // ... addImageToPage(pdf, 0, 0, 0, 1.0f, buffered); // example of how to set metadata on the document pdf.getDocumentInformation().setTitle("Modified by fileBridge"); } catch (Exception e) { slflog("error watermarking pdf in getContentStream ", e.getMessage()); } return pdf; } // Original code taken from Nick Russler's example here: // https://stackoverflow.com/questions/8929954/watermarking-with-pdfbox // and modified to use a BufferedImage directly. // Thank's Nick. // public void addImageToPage(PDDocument document, int pdfpage, int x, int y, float scale, BufferedImage tmp_image) throws IOException { // Convert the image to TYPE_4BYTE_ABGR so PDFBox won't throw exceptions // (e.g. for transparent png's). BufferedImage image = new BufferedImage(tmp_image.getWidth(), tmp_image.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); image.createGraphics().drawRenderedImage(tmp_image, null); PDXObjectImage ximage = new PDPixelMap(document, image); PDPage page = (PDPage) document.getDocumentCatalog().getAllPages().get(pdfpage); PDPageContentStream contentStream = new PDPageContentStream(document, page, true, true); contentStream.drawXObject(ximage, x, y, ximage.getWidth() * scale, ximage.getHeight() * scale); contentStream.close(); } }